最后更新时间:2019年6月17日
功能描述
路径引导,即导航功能,基于已有的规划路径实现导航,按照规划路径行进,辅以文字、图标引导,以及语音播报功能等。MapGIS Mobile 10.3 for Android SDK提供室内外一体化的路径引导接口,并且提供模拟导航,接入定位数据后可实现真实导航。此外,还能提供丰富的指引信息,比如当前道路的名称、当前导航动作、到达终点的距离等;提供路口放大图来准确指引路线;同时支持语音播报操作,提供更加友好、科学的智能化引导服务。
实现方法
路径引导(导航)功能实现的一般流程如下图所示:
1
构造路径引导对象RouteGuide,并通过init()方法初始化路径引导对象,设置路线对象Route、路径分析对象RouteAnalysis两个参数;
//构造路径引导对象 RouteGuide mRouteGuide = new RouteGuide(); //利用导航路径、路径分析对象来初始化路径引导对象,这两个对象在路径分析阶段即可获取 mRouteGuide.init(mRoute, mRouteAnalysis);
2
(1)如果进行真实导航:可调用startGNSSNavi()和stopGNSSNavi()来开始、停止。
//开始真实导航 mRouteGuide.startGNSSNavi(); //停止真实导航 mRouteGuide.stopGNSSNavi(); //GPS定位监听器 private LocationListener locationListener = new LocationListener() { //GPS位置改变时触发 @Override public void onLocationChanged(Location location) { //构建GNSS定位信息对象 GNSSLocInfo gnssLocInfo = new GNSSLocInfo(); gnssLocInfo.setLongitude(location.getLongitude()); //经度 gnssLocInfo.setLatitude(location.getLatitude()); //纬度 gnssLocInfo.setSpeed(location.getSpeed()); //速度 gnssLocInfo.setAngle(location.getBearing()); //角度 gnssLocInfo.sethDop(location.getAccuracy()); //精度 //向RouteGuide路径引导对象中推送定位信息 mRouteGuide.addGNSSLocInfo(gnssLocInfo); } };
代码说明:要实现真实导航,必须先实现实时定位功能,然后不断推送给RouteGuide对象。实时定位功能实现,可直接调用SDK的定位接口,也可采用Android原生定位接口实现,或者利用第三方定位SDK实现(如高德定位SDK、百度定位SDK等)。不管采用哪一种方式进行定位,其接口中一定会有定位的监听器,需要在其监听器的回调函数中实例化GNSSLocInfo对象,为其设置相关必要信息,然后设置给RouteGuide对象。
(2)如果进行模拟导航:可调用startSimNavi()、pauseOrResumeSimNavi()、stopSimNavi()来开始、暂停/继续、停止模拟导航。
//开始模拟导航 mRouteGuide.startSimNavi(); //暂停模拟导航 mRouteGuide.pauseOrResumeSimNavi(true); //继续模拟导航,暂停了之后才能继续 mRouteGuide.pauseOrResumeSimNavi(false); //停止模拟导航 mRouteGuide.stopSimNavi(); //加速、减速:改变模拟导航参数 //创建模拟导航的参数信息(模拟速度:公里/小时;生成模拟点的频率:秒) SimNaviOptions simNaviOptions=new SimNaviOptions(2 * 100f, 1.0f); //设置模拟导航的控制参数 mRouteGuide.setSimNaviOptions(simNaviOptions);
代码说明:模拟导航时需要传入模拟的运动速度,以及生成模拟点的频率,内部会根据这些参数模拟运动效果。
3
为路径引导对象设置RouteListener导航监听器,并在监听器的回调方法中获取导航指引信息,可将其以多种形式展现,如文本控件、图像视图控件等,给用户以提示。
导航,即路径引导,是一个实时、动态的过程,随着用户移动设备位置的变化,导航功能提供的信息也随之变化,所以指引信息也需要动态地获取。SDK目前没有提供默认的导航引导界面,指引信息的展示工作需要开发人员自己编码实现。SDK具有导航状态监听器,包括6个回调函数,每个函数提作用都不同,在其中提供了不同的指引信息。需要说明的是,这6个回调函数都是在子线程中执行的,如果获取信息后要操作UI界面,需要在主线程中执行。
//注册接收导航相关状态的监听器 mRouteGuide.registerListener(mRouteListener); //实例化导航状态监听器对象 class mRouteListener implements RouteListener { //导航指引信息(导航路径信息,真实/模拟导航) @Override public void onRouteNaviInfo(RouteNaviInfo routenaviinfo, boolean arg1) { //这是最重要、使用最多的一个回调函数,可获取诸多导航指引信息,例如当前道路名称、总里程剩余值、导航主动作等等,在下方会详细介绍各种方法如何获取、使用 } //真实导航状态变更回调(真实导航状态、导航状态辅助信息) @Override public void onGNSSNaviMode(GNSS_NAVI_MODE gnssNaviMode, int statusEx) { //可获取真实导航状态:开始、到达途经点、偏离路径、到达终点、停止。 switch (gnssNaviMode) { case GNSS_NAVI_MODE_START: Log.e("真实导航状态:", "导航开始"); break; case GNSS_NAVI_MODE_ARRIVE_MID: Log.e("真实导航状态:", "到达途径点"); break; case GNSS_NAVI_MODE_LEEWAY: Log.e("真实导航状态:", "偏离路径"); break; case GNSS_NAVI_MODE_STOP: Log.e("真实导航状态:", "导航停止"); break; case GNSS_NAVI_MODE_ARRIVE_DEST: Log.e("真实导航状态:", "到达终点"); break; default: break; } } //模拟导航状态变更回调(模拟导航状态) @Override public void onSimNaviMode(SIM_NAVI_MODE simNaviMode) { //可获取模拟导航状态:开始、进行中、暂停中、停止、完成。 switch (simNaviMode) { case SIM_NAVI_MODE_START: Log.e("模拟导航状态:", "模拟导航开始"); break; case SIM_NAVI_MODE_PROCESSING: Log.e("模拟导航状态:", "模拟导航进行中"); break; case SIM_NAVI_MODE_PAUSED: Log.e("模拟导航状态:", "模拟导暂停中"); break; case SIM_NAVI_MODE_STOP: Log.e("模拟导航状态:", "模拟导航停止"); break; case SIM_NAVI_MODE_FINISH: Log.e("模拟导航状态:", "模拟导航完成"); break; default: break; } } //处理定位信息 @Override public void onGNSSLocInfo(GNSSLocInfo gnssLocInfo, double matchLng, double matchLat, String matchFloor, float matchAngle) { //可获取:卫星状态、卫星数、道路匹配后的坐标、楼层、角度等信息 } //定位状态(GNSS状态) @Override public void onGNSSLocStatus(GNSS_LOC_STATUS locStatus) { //获取定位状态:设备是否已连接、已定位等 switch (locStatus) { case GNSS_LOC_STATUS_SEARCHING: Log.e("定位状态:", "设备搜索中"); break; case GNSS_LOC_STATUS_FIXED: Log.e("定位状态:", "设备已连接并已定位"); break; case GNSS_LOC_STATUS_FIXING: Log.e("定位状态:", "设备已连接,但未定位"); break; case GNSS_LOC_STATUS_DISCONNECT: Log.e("定位状态:", "设备不可用,例如移动端无GNSS模块,或者参数配置失败导致设备无法连接"); break; default: break; } } //播报指引信息(语音文字信息、真实/模拟导航、语音信息优先级) @Override public void onPlayNaviMessage(String message, boolean gnssNavi, int enPriority) { //获取导航指引语音信息,如:前方200米向左行驶等等 Log.e("播报指引信息:", message); //特别说明:MapGIS Mobile 10.3 for Android SDK没有提供内置的语音播报功能,用户可借助第三方开发库实现语音播报 } }
4
RouteListener导航相关状态监听器中有上述6个回调函数,其中最重要、最常用的函数是导航指引信息的函数onRouteNaviInfo,从此函数中可获取到导航路径信息RouteNaviInfo对象,由此对象获取很多导航过程中非常重要的指引信息。下面将详细介绍信息如何获取与其展示方法:
普通导航指引信息:即界面上提示的当前道路名称、总路程剩余距离、当前行驶速度等信息,这些信息从RouteNaviInfo对象中获取;获取信息后进行界面展示,让用户知道当前行驶的进度与状态。
//导航指引信息回调函数 @Override public void onRouteNaviInfo(RouteNaviInfo routenaviinfo, boolean arg1) { String roadName = routenaviinfo.curRoadName; //当前道路名称 int segDistance = routenaviinfo.segRemainDis; //当前路段剩余距离 int distance = routenaviinfo.remainDis; //总里程剩余距离 int remainTime = routenaviinfo.remainTime; //总导航剩余时间 float speed = routenaviinfo.speed; //当前行驶速度 String a = routenaviinfo.nextRoadName; //下一条道路的名称 }
如上述这些文字信息,开发人员可以在Activity界面上定义一些TextView文本控件,然后在回调函数中动态修改控件的文本值,即可实现提示作用。需注意这些回调函数都是在子线程中执行的,如果要修改UI控件,需返回到主线程再做操作。
导航动作信息:即路径引导中的方向信息,告知用户该向什么方向前进,如左转弯、右转弯等。导航动作信息也是从RouteNaviInfo对象中获取,得到short类型的16进制变量,不同的值对应不同的动作,具体见如下核心代码。
//导航指引信息回调函数 @Override public void onRouteNaviInfo(RouteNaviInfo routenaviinfo, boolean arg1) { //获取导航主动作 short action = routenaviinfo.naviAction; //定义一个int类型变量,存储对应图片资源的ID值 int directionId = 0; switch (action) { case 0x8: Log.e("导航主动作:", "直行"); directionId = R.mipmap.direction_line; break; case 0x1: Log.e("导航主动作:", "左转"); directionId = R.mipmap.direction_zz; break; case 0x2: Log.e("导航主动作:", "右转"); directionId = R.mipmap.direction_yz; break; ······ default: break; } }
代码说明:获取的导航主动作编码与主动作对应关系为:0x8(继续直行)、0x1(左转)0x2(右转)、0x9(靠左行驶)、0xA(靠右行驶)、0x7(左转调头)、0x0B(进入环岛)、0x0C(离开环岛)。室内专用导航主动作编码:0x10(沿楼梯向上)、0x11(沿楼梯向下)、0x12(沿电梯向上)、0x13(沿电梯向下)、0x14(进入建筑物)、0x15(离开建筑物)。
获取导航动作信息后,需要将此信息通过界面展示给用户,一般通过图片形式展现当前导航动作,能够非常直观地告知用户该往什么方向行驶。要实现此效果,通常在布局设计时添加一个ImageView图像视图控件,在获取导航动作信息之后,根据对应关系为ImageView动态设置图片。
运动目标的当前位置信息:导航过程中另外一个很重要的信息是行人或者车辆当前的位置,一般通过在地图上绘制图标GraphicImage来表示。由于导航是一个动态变化的过程,运动物体的位置、前进的方向在导航过程中是不断变化的,因此需要动态变更其位置点与行进角度。
//导航指引信息回调函数 @Override public void onRouteNaviInfo(RouteNaviInfo routenaviinfo, boolean arg1) { //获取当前位置点 Dot currentDot = routenaviinfo.getPosition(); //定位目标位置推算后的角度,与Y轴正方向的夹角,范围为[0,360],逆时针为正 float angle = routenaviinfo.angle; //地图的旋转角度,单位度,逆时针为正 float angle = 360 - angle; /* 动态修改定位图标的位置 说明:GraphicImage不能在监听回调中创建,避免多次进入,多次创建。需在导航之前就创建对象,在此监听中只需修改其位置即可。在此只是为展示完整的实现代码。 */ //开始导航前在地图上添加移动目标,作为导航目标的位置 GraphicImage locationGraphicImage = new GraphicImage(); //创建位图对象,并设置为GraphicImage Bitmap locationBmp = BitmapFactory.decodeResource(getResources(), R.mipmap.navigation_nogps); locationGraphicImage.setImage(locationBmp); //动态更新图像的位置,动态地展示导航运动过程 locationGraphicImage.setPoint(currentDot); locationGraphicImage.setAnchorPoint(new PointF(0.5f, 0.5f)); //设置锚点 mapView.getGraphicsOverlay().addGraphic(locationGraphicImage); //添加到覆盖物图层中显示 mapView.refresh(); //地图刷新 /* 动态修改地图的位置:将导航过程中当前位置设置为地图中心,并且旋转地图使得导航视角跟随车头,当然也可以不修改地图的位置、角度 */ MapPosition mapPositon = new MapPosition(currentDot, mapView.getResolution(), currentDot, angle, mapView.getSlopeAngle()); //地图位置(中心点,分辨率,旋转中心,旋转角,倾斜角) mapView.updatePosition(mapPositon, true); //更新位置 }
运动目标的地图位置显示效果如下图所示,可以根据路径的方向来改变地图的旋转角度,使导航视角跟随车头,与行人或者车辆真实情况中的朝向一致,营造一定程度的沉浸感。
路口放大图:将路口的分支情况以图片的形式放大展示,特别针对于复杂情况的路口,如典型的环岛,通过路口放大图,可以更加清晰地指引用户该往哪个方向行进。路口放大图的显示,需要自定义一个ImageView控件,通过SDK中的代码获取Bitmap位图,然后赋给图像视图控件,将当前路口放大图展示在界面中。
//导航指引信息回调函数 @Override public void onRouteNaviInfo(RouteNaviInfo routenaviinfo, boolean arg1) { //路口放大图背景ID int mCrossBackID = routenaviinfo.crossBackID; if (getCrossImage(mCrossBackID) != null) { //如果获取的路口放大图Bitmap不为空,则赋给图像视图控件对象GraphicImage(说明:UI操作需在主线程执行,在此省略) crossGuideImgview.setImageBitmap(crossBitmap); } } //获取路口放大图Bitmap的自定义方法 private Bitmap getCrossImage(int crossBackID) { //位图 Bitmap bitmap = null; //如果地图视图对象、路线对象有一者为空,则无法创建路口放大图 if (mRoute == null || mapView == null) { return null; } //如果背景ID小于0,不存在放大图;如果背景ID大于减2后的路段总数,则不构建放大图 if (crossBackID < 0 || crossBackID > mRoute.getStepCount() - 2) { return null; } //根据ID获取当前路段、下一路段、下下路段 DriveWalkSegment driveWalkSegment = (DriveWalkSegment) mRoute.getStep(crossBackID); DriveWalkSegment driveWalkSegmentNext = (DriveWalkSegment) mRoute.getStep(crossBackID + 1); DriveWalkSegment driveWalkSegmentNextNext = null; //获取路段的节点数组 Dot[] curSegDots = driveWalkSegment.getShapes(); Dot[] nextSegDots = driveWalkSegmentNext.getShapes(); Dot[] nextNextSegDots = null; //判断路段是否为环岛 if (!driveWalkSegmentNext.getForm().contains("环岛")) { //普通路口 //根据地图视图大小构建位图,像素格式必须为ARGB_8888 bitmap = Bitmap.createBitmap(mapView.getWidth(), mapView.getHeight() / 3, Bitmap.Config.ARGB_8888); //利用MapView根据传入的参数生成对应路口的放大图 if (mMapView.getCrossBitmap(curSegDots, nextSegDots, nextNextSegDots, bitmap) == 1) { return bitmap; } } else { //如果为环岛,需特殊处理 //如果背景ID大于路径的路段数减3,则不构建放大图 if (crossBackID > mRoute.getStepCount() - 3) { return null; } //获取当前路段的下下条路段,以及路段的点数组 driveWalkSegmentNextNext = (DriveWalkSegment) mRoute.getStep(crossBackID + 2); nextNextSegDots = driveWalkSegmentNextNext.getShapes(); //根据地图视图大小构建位图,像素格式必须为ARGB_8888 bitmap = Bitmap.createBitmap(mapView.getWidth(), mapView.getHeight() / 3, Bitmap.Config.ARGB_8888); //利用MapView根据传入的参数生成对应路口的放大图 if (mMapView.getCrossBitmap(curSegDots, nextSegDots, nextNextSegDots, bitmap) == 1) { return bitmap; } } return bitmap; }
代码说明:路口放大图主要分为普通路口、环岛路口两种,对于环岛需要特殊处理。路口放大图的生成,需要使用MapView地图控件的getCrossBitmap方法实现。例如,普通路口放大图效果如下图所示。